package org.wzy.spring.framework.webmvc.servlet;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wzy.spring.framework.annotation.Controller;
import org.wzy.spring.framework.annotation.RequestMapping;
import org.wzy.spring.framework.annotation.RequestParam;
import org.wzy.spring.framework.context.AbstractApplicationContext;
import org.wzy.spring.framework.context.ApplicationContext;
import org.wzy.spring.framework.context.ClassPathXmlApplicationContext;
import org.wzy.spring.framework.util.AopUtils;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * DispatcherServlet, 作为MVC启动的入口
 *
 * @author wzy_h
 * @version v_1.0
 * @date 2018/5/19 22:42
 * @since 1.8
 */
public class DispatcherServlet extends HttpServlet {

    private static final long serialVersionUID = 3292278521162774489L;
    /**
     * 在 Spring 中, configLocations 字段的信息是从 web.xml 中读取出来的, 这里为了方便直接写死为固定文件
     */
    private final String configLocations = "application.properties";
    private Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);
    /**
     * ApplicationContext 对象
     */
    private ApplicationContext applicationContext;
    /**
     * handlerMapping 集合
     */
    private List<HandlerMapping> handlerMappings = new ArrayList<>();

    /**
     * viewResolver 集合
     */
    private List<ViewResolver> viewResolvers = new ArrayList<>();

    /**
     * handlerAdapter 集合, 在 SpringMVC 中 handlerAdapters 是一个 List 集合, 这里为了方便改为 Map, 直接关联到 HandlerMapping
     */
    private Map<HandlerMapping, HandlerAdapter> handlerAdapters = new ConcurrentHashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            writeException(e, resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            writeException(e, resp);
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
        HandlerMapping handlerMapping = getHandler(req);
        if (handlerMapping == null) {
            resp.getWriter().write("<font size='18px' color='blue'>404 Not Found! @SpringManuallyMVC</font>");
            return;
        }
        HandlerAdapter handlerAdapter = getHandlerAdapter(handlerMapping);
        if (null == handlerAdapter) {
            resp.getWriter().write("<font size='18px' color='blue'>404 Not Found! @SpringManuallyMVC</font>");
            return;
        }

        // 这一步调用方法
        ModelAndView modelAndView = handlerAdapter.handle(req, resp, handlerMapping);

        // 这一步处理页面渲染及结果输出
        processDispatchResult(resp, modelAndView);
    }

    private HandlerMapping getHandler(HttpServletRequest req) {
        if (this.handlerMappings.isEmpty()) {
            return null;
        }
        String requestURI = req.getRequestURI();
        String contextPath = req.getContextPath();
        logger.info("doDispatch -> getHandler -> requestURI={}, contextPath={}", requestURI, contextPath);

        // 替换掉多余的斜杠
        requestURI = requestURI.replace(contextPath, StringUtils.EMPTY).replaceAll("/+", "/");
        for (HandlerMapping handlerMapping : handlerMappings) {
            Matcher matcher = handlerMapping.getPattern().matcher(requestURI);
            if (matcher.matches()) {
                logger.info("requestURI={} matches HandlerMapping[{}]", requestURI, handlerMapping);
                return handlerMapping;
            }
        }

        // unexpected.
        return null;
    }

    private HandlerAdapter getHandlerAdapter(HandlerMapping handlerMapping) {
        if (this.handlerAdapters.isEmpty()) {
            return null;
        }

        return this.handlerAdapters.get(handlerMapping);
    }

    /**
     * 真正的输出方法, 利用 Response 对象将结果写回到前台
     *
     * @param resp         response
     * @param modelAndView modelAndView
     * @author 王政豫
     * @date 2018/5/24 15:40
     */
    private void processDispatchResult(HttpServletResponse resp, ModelAndView modelAndView) throws IOException {
        if (null == modelAndView || this.viewResolvers.isEmpty()) {
            return;
        }

        for (ViewResolver viewResolver : this.viewResolvers) {
            String viewName = modelAndView.getViewName();
            if (viewResolver.getFileName().equals(viewName)) {
                // 说明之前扫描到了这个文件
                try {
                    String s = viewResolver.resolveViewName(modelAndView);
                    resp.setContentType("UTF-8");
                    resp.getWriter().write(s);
                } catch (Exception ex) {
                    writeException(ex, resp);
                }
            }
        }
    }

    /**
     * Servlet 初始化方法, 所有有关 Spring IOC 容器的初始化、依赖注入过程都是在此方法中完成
     *
     * @author wzy_h
     * @date 2018/5/19 22:48
     * @since 1.8
     */
    @Override
    public void init(ServletConfig config) {
        try {
            /* 1.首先初始化 IOC 容器, 因为 SpringMVC 中的组件需要在 IOC 容器初始化之后执行(主要是解决依赖关系) */
            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(new String[]{configLocations});

            logger.info("<=== Spring IOC 容器初始化完毕, 准备加载 SpringMVC 组件...");

            /* 2.然后初始化 SpringMVC 组件, 源码中是由 DispatcherServlet 重写 onRefresh() 方法来实现的 */
            initStrategies(applicationContext);

            /* 注入完成之后的测试, 打印的 service 所在内存地址应该一样, 并且若注入失败的话调用 service 方法会出现异常 */
//            Map<String, Object> singletonBeanCacheMap = applicationContext.getSingletonBeanCacheMap();
//            DemoController demoController = (DemoController) singletonBeanCacheMap.get("org.wzy.spring.framework.webmvc.demo.controller.DemoController");
//            demoController.test("我是张三!!!");
//            DemoController1 demoController1 = (DemoController1) singletonBeanCacheMap.get("org.wzy.spring.framework.webmvc.demo.controller.DemoController1");
//            demoController1.test("我是李四!!!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * Strategy 策略模式, 一共9种策略, 每个请求都会经过一系列策略干预之后输出结果, 每个策略都可以自定义干预, 但是最终结果都是一致的
     *
     * @param context ApplicationContext 上下文
     * @author 王政豫
     * @date 2018/5/22 15:00
     */
    private void initStrategies(ApplicationContext context) throws Exception {
        // 文件上传解析, 如果请求类型是multipart将通过MultipartResolver进行文件上传解析
        initMultipartResolver(context);

        // 本地化(国际化)解析
        initLocaleResolver(context);

        // 主题解析(SpringMVC 支持动态主题切换)
        initThemeResolver(context);

        // HandlerMapping 用来保存 Controller 中配置的 RequestMapping 和 Method 的对应关系
        // 通过HandlerMapping, 将请求映射到对应的处理器
        initHandlerMappings(context);

        // HandlerAdapters 用来动态匹配 Method 参数, 包括类转换, 动态赋值
        // 通过 HandlerAdapter 进行多类型的参数动态匹配
        initHandlerAdapters(context);

        // 如果执行过程中遇到异常, 将交给HandlerExceptionResolver来解析
        initHandlerExceptionResolvers(context);

        // 直接解析请求到视图名
        initRequestToViewNameTranslator(context);

        // 通过viewResolver解析逻辑视图到具体视图实现
        initViewResolvers(context);

        // flash 映射管理器
        initFlashMapManager(context);

    }

    private void initFlashMapManager(ApplicationContext context) {
    }

    private void initViewResolvers(ApplicationContext context) {
        // 解决页面名称和模板文件关联的问题
        if (context instanceof AbstractApplicationContext) {
            AbstractApplicationContext abstractApplicationContext = (AbstractApplicationContext) context;
            Properties configurations = abstractApplicationContext.getConfigurations();
            String templateRoot = configurations.getProperty("templateRoot");
            File templateRootFile = new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource(templateRoot)).getFile());
            for (File file : Objects.requireNonNull(templateRootFile.listFiles())) {
                this.viewResolvers.add(new ViewResolver(file.getName(), file));
            }
        }
    }

    private void initRequestToViewNameTranslator(ApplicationContext context) {
    }

    private void initHandlerExceptionResolvers(ApplicationContext context) {
    }

    private void initHandlerAdapters(ApplicationContext context) {
        // 在初始化阶段, 可以将参数的名字或类名按照一定的排序规则保存, 而后在反射调用时传入的形参是一个数组
        // 可以通过记录这些参数的下标, 依次从数组中填值, 由此一来就和参数的顺序无关了
        for (HandlerMapping handlerMapping : this.handlerMappings) {
            // 每个方法对应一个参数列表, 这里保存的是形参
            Map<String, Integer> parameterMap = new HashMap<>(10);

            /* 处理命名参数 */
            // 每个参数都可以声明多个注解, 因此这里需要使用二维数组
            Annotation[][] parameterAnnotations = handlerMapping.getMethod().getParameterAnnotations();
            for (int i = 0; i < parameterAnnotations.length; i++) {
                for (Annotation annotation : parameterAnnotations[i]) {
                    // 没有声明 @RequestParam 注解的参数不赋值
                    if (annotation instanceof RequestParam) {
                        String parameterName = ((RequestParam) annotation).value();
                        if (StringUtils.isEmpty(parameterName)) {
                            continue;
                        }
                        parameterMap.put(parameterName, i);
                    }
                }
            }

            /* 处理非命名参数 (Request, Response) */
            Class<?>[] parameterTypes = handlerMapping.getMethod().getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                if (parameterTypes[i] == HttpServletRequest.class || parameterTypes[i] == HttpServletResponse.class) {
                    parameterMap.put(parameterTypes[i].getName(), i);
                }
            }

            this.handlerAdapters.put(handlerMapping, new HandlerAdapter(parameterMap));
        }
    }

    private void initHandlerMappings(ApplicationContext context) throws Exception {
        String[] beanDefinitionNames = new String[]{};
        if (context instanceof AbstractApplicationContext) {
            AbstractApplicationContext abstractApplicationContext = (AbstractApplicationContext) context;
            beanDefinitionNames = abstractApplicationContext.getBeanDefinitionNames();
        }

        for (String beanDefinitionName : beanDefinitionNames) {
            // 此处获取到的可能是一个被代理后的对象
            Object proxiedInstance = context.getBean(beanDefinitionName);
            // 获取代理对象的原始对象, 即真正的 Controller 对象
            Object originalInstance = AopUtils.getTargetObject(proxiedInstance);
            Class<?> instanceClass = originalInstance.getClass();

            // 首先需要获取哪些 bean 声明了 @Controller 注解
            if (!instanceClass.isAnnotationPresent(Controller.class)) {
                continue;
            }

            String requestUrl = StringUtils.EMPTY;
            if (instanceClass.isAnnotationPresent(RequestMapping.class)) {
                // 获取 url 映射
                RequestMapping requestMapping = instanceClass.getAnnotation(RequestMapping.class);
                requestUrl = StringUtils.isNotEmpty(requestMapping.value().trim()) ? requestMapping.value().trim() : StringUtils.EMPTY;
                if (StringUtils.isNotEmpty(requestUrl) && !requestUrl.startsWith("/")) {
                    requestUrl = "/" + requestUrl;
                }
            }

            // 还需要获取所有使用 @RequestMapping 注解的 public 方法, 这样才能拼凑起来一个完整的 url
            Method[] methods = instanceClass.getMethods();
            String methodUrl;
            for (Method method : methods) {
                if (!method.isAnnotationPresent(RequestMapping.class)) {
                    continue;
                }

                methodUrl = method.getAnnotation(RequestMapping.class).value();
                methodUrl = StringUtils.isNotEmpty(methodUrl) ? methodUrl.trim() : StringUtils.EMPTY;
                if (StringUtils.isBlank(methodUrl)) {
                    String declaredMethodClassName = method.getDeclaringClass().getName();
                    String methodName = method.getName();
                    logger.error("Class -> [{}], public method -> [{}] 声明了 @RequestMapping 注解却没有提供 value(url), 将不会被纳入 SpringMVC 的管理中!",
                            declaredMethodClassName, methodName);
                    continue;
                }

                StringBuilder sb = new StringBuilder();
                if (!methodUrl.startsWith("/")) {
                    methodUrl = sb.append("/").append(methodUrl).toString();
                }
                String regex = (requestUrl + methodUrl.replaceAll("\\*", ".*")).replaceAll("/+", "/");
                this.handlerMappings.add(new HandlerMapping(Pattern.compile(regex), originalInstance, method));

                logger.info("Mapped[{}] on {}#{}", regex, originalInstance.getClass().getName(), method.getName());
            }
        }
    }

    private void initThemeResolver(ApplicationContext context) {
    }

    private void initLocaleResolver(ApplicationContext context) {
    }

    private void initMultipartResolver(ApplicationContext context) {
    }

    /**
     * 向前台输出异常内容
     *
     * @param ex   异常对象
     * @param resp response 对象
     * @throws IOException 输出流异常
     * @date 2018/5/24 23:17
     * @since 1.8
     */
    private void writeException(Exception ex, HttpServletResponse resp) throws IOException {
        ex.printStackTrace();
        resp.getWriter().write("<font size='18px' color='red'>500 Exception... @SpringManuallyMVC</font><br/>"
                + ex.getCause() + "<br/>" + Arrays.toString(ex.getStackTrace()).replaceAll("\\[", "")
                .replaceAll("]", "").replaceAll(", ", "<br/>"));
    }
}
